<!DOCTYPE html>
<meta charset=utf-8>
<title>Test play state changes for animations with a negative playback rate</title>
<link rel="help" href="https://drafts.csswg.org/web-animations/#play-state">
<script src="../../external/wpt/web-animations/testcommon.js"></script>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>

<script>
var duration = 100000;

function assert_unresolved(value) {
  assert_equals(value, null);
}

function idleAnimation() {
  var animation = document.documentElement.animate([], duration);
  animation.reverse();
  animation.cancel();
  return animation;
}

function runningAnimation() {
  var animation = idleAnimation();
  animation.play();
  animation.startTime = document.timeline.currentTime + duration / 2;
  return animation;
}

function pendingAnimation() {
  var animation = idleAnimation();
  animation.play();
  return animation;
}

function pausedAnimation() {
  var animation = idleAnimation();
  animation.pause();
  animation.currentTime = duration;
  return animation;
}

function finishedAnimation() {
  var animation = idleAnimation();
  animation.play();
  animation.finish();
  return animation;
}

test(function() {
  var animation = idleAnimation();
  assert_unresolved(animation.startTime);
  assert_unresolved(animation.currentTime);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'idle');
}, "Play state is idle after cancelling a reversed animation");

test(function() {
  var animation = pendingAnimation();
  assert_unresolved(animation.startTime);
  assert_equals(animation.currentTime, duration);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Play state is running after playing a canceled reversed animation");

test(function() {
  var animation = runningAnimation();
  assert_times_equal(animation.startTime, document.timeline.currentTime - (animation.playbackRate * animation.currentTime));
  assert_times_equal(animation.currentTime, duration / 2);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Play state is running after playing and setting start time of a canceled reversed animation");

test(function() {
  var animation = pausedAnimation();
  assert_unresolved(animation.startTime);
  assert_equals(animation.currentTime, duration);
  // Setting the current time of a pause-pending animation applies a synchronous update.
  assert_false(animation.pending);
  assert_equals(animation.playState, 'paused');
}, "Play state is paused after pausing and setting current time of a canceled reversed animation");

test(function() {
  var animation = finishedAnimation();
  assert_times_equal(
      animation.startTime,
      document.timeline.currentTime - (animation.playbackRate * animation.currentTime));
  assert_equals(animation.currentTime, 0);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'finished');
}, "Play state is finished after playing and finishing a cancelled reversed animation");

test(function() {
  var animation = idleAnimation();
  animation.play();
  assert_unresolved(animation.startTime);
  assert_equals(animation.currentTime, duration);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Calling play() on an idle animation");

test(function() {
  var animation = idleAnimation();
  animation.pause();
  assert_unresolved(animation.startTime);
  assert_equals(animation.currentTime, duration);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'paused');
}, "Calling pause() on an idle animation");

test(function() {
  var animation = idleAnimation();
  animation.cancel();
  assert_unresolved(animation.startTime);
  assert_unresolved(animation.currentTime);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'idle');
}, "Calling cancel() on an idle animation");

test(function() {
  var animation = idleAnimation();
  animation.finish();
  assert_times_equal(
      animation.startTime,
      document.timeline.currentTime - (animation.playbackRate * animation.currentTime));
  assert_equals(animation.currentTime, 0);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'finished');
}, "Calling finish() on an idle animation");

test(function() {
  var animation = idleAnimation();
  animation.reverse();
  assert_unresolved(animation.startTime);
  assert_equals(animation.currentTime, 0);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Calling reverse() on an idle animation");

test(function() {
  var animation = idleAnimation();
  animation.currentTime = 1000;
  assert_unresolved(animation.startTime);
  assert_equals(animation.currentTime, 1000);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'paused');
}, "Setting currentTime on an idle animation");

test(function() {
  var animation = idleAnimation();
  animation.startTime = document.timeline.currentTime + 1000;
  assert_times_equal(animation.startTime, document.timeline.currentTime + 1000);
  assert_times_equal(animation.currentTime, 1000);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Setting startTime on an idle animation");

test(function() {
  var animation = pendingAnimation();
  animation.play();
  assert_unresolved(animation.startTime);
  assert_equals(animation.currentTime, duration);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Calling play() on a pending animation");

test(function() {
  var animation = pendingAnimation();
  animation.pause();
  assert_unresolved(animation.startTime);
  assert_equals(animation.currentTime, duration);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'paused');
}, "Calling pause() on a pending animation");

test(function() {
  var animation = pendingAnimation();
  animation.cancel();
  assert_unresolved(animation.startTime);
  assert_unresolved(animation.currentTime);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'idle');
}, "Calling cancel() on a pending animation");

test(function() {
  var animation = pendingAnimation();
  animation.finish();
  assert_times_equal(animation.startTime, document.timeline.currentTime - (animation.playbackRate * animation.currentTime));
  assert_equals(animation.currentTime, 0);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'finished');
}, "Calling finish() on a pending animation");

test(function() {
  var animation = pendingAnimation();
  animation.reverse();
  assert_unresolved(animation.startTime);
  assert_equals(animation.currentTime, 0);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Calling reverse() on a pending animation");

test(function() {
  var animation = pendingAnimation();
  animation.currentTime = 1000;
  assert_unresolved(animation.startTime);
  assert_equals(animation.currentTime, 1000);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Setting currentTime on a pending animation");

test(function() {
  var animation = pendingAnimation();
  animation.startTime = document.timeline.currentTime + 1000;
  assert_times_equal(animation.startTime, document.timeline.currentTime + 1000);
  assert_times_equal(animation.currentTime, 1000);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Setting startTime on a pending animation");

test(function() {
  var animation = runningAnimation();
  var startTime = animation.startTime;
  var currentTime = animation.currentTime;
  animation.play();
  assert_equals(animation.startTime, startTime);
  assert_equals(animation.currentTime, currentTime);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Calling play() on a running animation");

async_test(function(t) {
  var animation = runningAnimation();
  animation.pause();
  assert_true(animation.pending);
  assert_times_equal(animation.currentTime, duration / 2);
  assert_equals(animation.playState, 'paused');
  animation.ready.then(t.step_func_done(() => {
    assert_equals(animation.startTime, null);
    assert_false(animation.pending);
    assert_equals(animation.playState, 'paused');
  }));
}, "Calling pause() on a running animation");

test(function() {
  var animation = runningAnimation();
  animation.cancel();
  assert_unresolved(animation.startTime);
  assert_unresolved(animation.currentTime);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'idle');
}, "Calling cancel() on a running animation");

test(function() {
  var animation = runningAnimation();
  animation.finish();
  assert_times_equal(animation.startTime, document.timeline.currentTime - (animation.playbackRate * animation.currentTime));
  assert_equals(animation.currentTime, 0);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'finished');
}, "Calling finish() on a running animation");

async_test(function(t) {
  var animation = runningAnimation();
  assert_equals(animation.playbackRate, -1);
  animation.reverse();
  assert_true(animation.pending);
  assert_times_equal(animation.currentTime, duration / 2);
  assert_equals(animation.playbackRate, -1);
  assert_equals(animation.playState, 'running');
  animation.ready.then(t.step_func_done(() => {
    assert_false(animation.pending);
    // TODO(crbug.com/960944): Add check to ensure that current time remains
    // unchanged once remaining timing glitches are resolved.
    assert_not_equals(animation.startTime, null);
    assert_equals(animation.playbackRate, 1);
    assert_equals(animation.playState, 'running');
  }));
}, "Calling reverse() on a running animation");

test(function() {
  var animation = runningAnimation();
  animation.currentTime = 1000;
  assert_times_equal(animation.startTime, document.timeline.currentTime - (animation.playbackRate * animation.currentTime));
  assert_times_equal(animation.currentTime, 1000);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Setting currentTime on a running animation");

test(function() {
  var animation = runningAnimation();
  animation.startTime = document.timeline.currentTime + 1000;
  assert_times_equal(animation.startTime, document.timeline.currentTime + 1000);
  assert_times_equal(animation.currentTime, 1000);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Setting startTime on a running animation");

test(function() {
  var animation = pausedAnimation();
  animation.play();
  assert_unresolved(animation.startTime);
  assert_equals(animation.currentTime, duration);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Calling play() on a paused animation");

test(function() {
  var animation = pausedAnimation();
  // Calling pause on an animation that is already paused or pause-pending is
  // a no-op.
  animation.pause();
  assert_unresolved(animation.startTime);
  assert_equals(animation.currentTime, duration);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'paused');
}, "Calling pause() on a paused animation");

test(function() {
  var animation = pausedAnimation();
  animation.cancel();
  assert_unresolved(animation.startTime);
  assert_unresolved(animation.currentTime);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'idle');
}, "Calling cancel() on a paused animation");

test(function() {
  var animation = pausedAnimation();
  animation.finish();
  assert_times_equal(animation.startTime, document.timeline.currentTime - (animation.playbackRate * animation.currentTime));
  assert_equals(animation.currentTime, 0);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'finished');
}, "Calling finish() on a paused animation");

test(function() {
  var animation = pausedAnimation();
  animation.reverse();
  assert_unresolved(animation.startTime);
  assert_equals(animation.currentTime, 0);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Calling reverse() on a paused animation");

test(function() {
  var animation = pausedAnimation();
  // Setting the current time on a paused or pause-pending animation gets
  // resolved synchronously.
  animation.currentTime = 1000;
  assert_unresolved(animation.startTime);
  assert_times_equal(animation.currentTime, 1000);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'paused');
}, "Setting currentTime on a paused animation");

test(function() {
  var animation = pausedAnimation();
  animation.startTime = document.timeline.currentTime + 1000;
  assert_times_equal(animation.startTime, document.timeline.currentTime + 1000);
  assert_times_equal(animation.currentTime, 1000);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Setting startTime on a paused animation");

test(function() {
  var animation = finishedAnimation();
  animation.play();
  assert_unresolved(animation.startTime);
  assert_equals(animation.currentTime, duration);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Calling play() on a finished animation");

async_test(function(t) {
  var animation = finishedAnimation();
  animation.pause();
  assert_equals(animation.currentTime, 0);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'paused');
  animation.ready.then(t.step_func_done(() => {
    assert_equals(animation.startTime, null);
    assert_equals(animation.currentTime, 0);
    assert_false(animation.pending);
    assert_equals(animation.playState, 'paused');
  }));
}, "Calling pause() on a finished animation");

test(function() {
  var animation = finishedAnimation();
  animation.cancel();
  assert_unresolved(animation.startTime);
  assert_unresolved(animation.currentTime);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'idle');
}, "Calling cancel() on a finished animation");

test(function() {
  var animation = finishedAnimation();
  animation.finish();
  assert_times_equal(animation.startTime, document.timeline.currentTime - (animation.playbackRate * animation.currentTime));
  assert_equals(animation.currentTime, 0);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'finished');
}, "Calling finish() on a finished animation");

async_test(function(t) {
  var animation = finishedAnimation();
  animation.reverse();
  assert_equals(animation.startTime, null);
  assert_equals(animation.currentTime, 0);
  assert_equals(animation.playbackRate, -1);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'running');
  animation.ready.then(t.step_func_done(() => {
    assert_false(animation.pending);
    assert_not_equals(animation.startTime, null);
    assert_greater_than_equal(animation.currentTime, 0);
    assert_equals(animation.playbackRate, 1);
    assert_equals(animation.playState, 'running');
  }));

}, "Calling reverse() on a finished animation");

test(function() {
  var animation = finishedAnimation();
  animation.currentTime = 1000;
  assert_times_equal(animation.startTime, document.timeline.currentTime - (animation.playbackRate * animation.currentTime));
  assert_times_equal(animation.currentTime, 1000);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Setting currentTime on a finished animation");
</script>
